// part1:C13:13:26:myStrBlob.h
#ifndef MY_STRBLOB_H__
#define MY_STRBLOB_H__
#include <vector>
#include <initializer_list>
#include <memory>
#include <stdexcept>
using namespace std;
class StrBlobPtr;
class StrBlob
{
  friend class StrBlobPtr;
public:
  typedef vector<string>::size_type size_type;
  StrBlob();
  StrBlob(initializer_list<string> il);
  StrBlob(vector<string> *p);
  StrBlob(StrBlob &s);
  StrBlob& operator=(StrBlob &rhs);
  size_type size() const { return data->size(); }
  bool empty() const { return data->empty(); }
  // add and remove elements.
  void push_back(const string &t) { data->push_back(t); }
  void pop_back();
  // travel elemets.
  string& front();
  const string& front() const;
  string& back();
  const string& back() const;
  // StrBlobPtr interface.
  StrBlobPtr begin();
  StrBlobPtr end();
  // const version
  StrBlobPtr begin() const;
  StrBlobPtr end() const;
private:
  shared_ptr<std::vector<std::string>> data;
  void check(size_type i, const std::string &msg) const;
};

inline StrBlob::StrBlob(): data(make_shared<vector<string>>()){}
inline StrBlob::StrBlob(initializer_list<string> il):
  data(make_shared<vector<string>>(il)) {}
inline StrBlob::StrBlob(vector<string> *p): data(p){}
inline StrBlob::StrBlob(StrBlob &s): data(make_shared<vector<string>>(*s.data)) {}

inline StrBlob& StrBlob::operator=(StrBlob &rhs)
{
  data = make_shared<vector<string>>(*rhs.data);
  return *this;
}

inline void StrBlob::check(size_type i, const string &msg) const
{
  if (i >= data->size())
  {
    throw out_of_range(msg);
  }
}

inline string& StrBlob::front()
{
  check(0, "front on empty StrBlob!");
  return data->front();
}

inline const string& StrBlob::front() const
{
  check(0, "front on empty StrBlob!");
  return data->front();
}

inline string& StrBlob::back()
{
  check(0, "back on empty StrBlob!");
  return data->back();
}

// const version back.
inline const string& StrBlob::back() const
{
  check(0, "back on empty StrBlob");
  return data->back();
}

inline void StrBlob::pop_back()
{
  check(0, "pop_back on empty StrBlob");
  data->pop_back();
}

// when try to access no element, StrBlob throw an exception.
class StrBlobPtr
{
  friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
  StrBlobPtr():curr(0){}
  StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz){}
  StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz){}

  string& deref() const;
  string& deref(int off) const;
  StrBlobPtr& incr();
  StrBlobPtr& decr();
private:  
  // successful, check return a shared_ptr, which point to vector.
  shared_ptr<vector<string>> 
    check(size_t, const string&) const;
  // store weak_ptr, the vector would be destroyed.
  weak_ptr<vector<string>> wptr;
  size_t curr;
};

inline shared_ptr<vector<string>> 
    StrBlobPtr::check(size_t i, const string& msg) const
{
  auto ret = wptr.lock();
  if (!ret)
  {
    throw runtime_error("unbound StrBlobPtr");
  }

  if (i >= ret->size())
  {
    throw out_of_range(msg);
  }
  return ret;
}

inline string& StrBlobPtr::deref() const
{
  auto p = check(curr, "dereference past end");
  return (*p)[curr];
}

inline string& StrBlobPtr::deref(int off) const
{
  auto p = check(curr + off, "dereference past end");
  return (*p)[curr + off];
}

// after increment, return reference of object.
inline StrBlobPtr& StrBlobPtr::incr()
{
  check(curr, "increment past end fo StrBlobPtr");
  ++curr;
  return *this;
}

// after decrement, return reference of object.
inline StrBlobPtr& StrBlobPtr::decr()
{
  --curr;
  check(-1, "decrement past begin of StrBlobPtr");
  return *this;
}

// begin of StrBlob and end of StrBlob
inline StrBlobPtr StrBlob::begin()
{
  return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end()
{
  auto ret = StrBlobPtr(*this, data->size());
  return ret;
}

// const version
inline StrBlobPtr StrBlob::begin() const
{
  return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end() const
{
  auto ret = StrBlobPtr(*this, data->size());
  return ret;
}

// compare of StrBlob
inline bool eq(const StrBlobPtr &lhs,const StrBlobPtr &rhs)
{
  auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
  // if vector are the same
  if (l == r)
  {
    // two are empty, or they are equal
    return (!r || lhs.curr == rhs.curr);
  }
  else
  {
    return false;
  }
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
  return !eq(lhs, rhs);
}
#endif
